package com.ripcore.engine;

import com.ripcore.model.RIPPacket;
import com.ripcore.model.RouteEntry;
import com.ripcore.model.Router;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 默认的RIP协议模拟引擎实现类
 * 负责管理路由器、执行RIP协议逻辑、处理路由更新和维护路由表状态
 */
public class DefaultSimulationEngine implements SimulationEngine {

    /** 存储所有路由器的线程安全映射，键为路由器ID */
    private final Map<String, Router> routers = new ConcurrentHashMap<>();
    
    /** 定时任务调度器，用于执行周期性更新和超时检查 */
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
    
    /** 标识模拟引擎是否正在运行的标志位 */
    private volatile boolean running = false;
    
    /** 模拟事件监听器，用于通知路由表变化等事件 */
    private SimulationEventListener listener;
    
    /** 模拟会话的唯一标识符 */
    private final String simulationId;
    
    /** 模拟配置参数，包含RIP协议的各项配置 */
    private final SimulationConfig config;

    /** 
     * 路由抑制时间跟踪映射
     * 结构：routerId -> (destination -> holdExpireInstant)
     * 用于实现RIP协议的路由抑制机制，防止路由抖动
     */
    private final Map<String, Map<String, Instant>> holdDownExpiry = new ConcurrentHashMap<>();

    /**
     * 构造函数，初始化模拟引擎
     * @param simulationId 模拟会话ID
     * @param config 模拟配置参数
     */
    public DefaultSimulationEngine(String simulationId, SimulationConfig config) {
        this.simulationId = simulationId;
        this.config = config;
    }

    /**
     * 启动模拟引擎
     * 开始执行周期性的路由更新和超时检查任务
     */
    @Override
    public void start() {
        if (running) return;
        running = true;
        // 启动周期性路由更新任务，立即执行，按配置间隔重复
        scheduler.scheduleAtFixedRate(this::periodicUpdate, 0, config.getUpdateIntervalSeconds(), TimeUnit.SECONDS);
        // 启动路由超时和抑制检查任务，延迟5秒执行，每5秒重复一次
        scheduler.scheduleAtFixedRate(this::checkTimeoutsAndHolds, 5, 5, TimeUnit.SECONDS);
    }

    /**
     * 停止模拟引擎
     * 关闭定时任务调度器并停止所有模拟活动
     */
    @Override
    public void stop() {
        running = false;
        scheduler.shutdownNow();
    }

    /**
     * 重置模拟状态
     * 清空所有路由器和相关的路由抑制信息
     */
    @Override
    public void reset() {
        routers.clear();
        holdDownExpiry.clear();
    }

    /**
     * 向模拟环境中添加路由器
     * @param r 要添加的路由器实例
     */
    @Override
    public void addRouter(Router r) {
        routers.put(r.getId(), r);
        // 为新添加的路由器创建本地直连路由条目（度量值为0）
        RouteEntry local = new RouteEntry();
        local.setDestination(r.getId());
        local.setMetric(0);
        local.setNextHop(r.getId());
        local.setLastUpdated(Instant.now());
        r.getRoutingTable().put(r.getId(), local);
        // 初始化该路由器的路由抑制映射
        holdDownExpiry.putIfAbsent(r.getId(), new ConcurrentHashMap<>());
    }

    /**
     * 从模拟环境中移除指定路由器
     * @param routerId 要移除的路由器ID
     */
    @Override
    public void removeRouter(String routerId) {
        routers.remove(routerId);
        holdDownExpiry.remove(routerId);
    }

    /**
     * 根据ID获取路由器实例
     * @param routerId 路由器ID
     * @return 对应的路由器实例，如果不存在则返回null
     */
    @Override
    public Router getRouter(String routerId) { return routers.get(routerId); }

    /**
     * 获取模拟环境中所有路由器的列表
     * @return 路由器列表的副本
     */
    @Override
    public List<Router> listRouters() { return new ArrayList<>(routers.values()); }

    /**
     * 执行一次模拟步骤
     * 推进模拟过程，执行一次路由更新和超时检查
     */
    @Override
    public void stepOnce() {
        periodicUpdate();
        checkTimeoutsAndHolds();
    }

    /**
     * 设置模拟事件监听器
     * @param listener 事件监听器实例
     */
    @Override
    public void setListener(SimulationEventListener listener) { this.listener = listener; }

    /**
     * 执行周期性路由更新
     * 遍历所有路由器，向其邻居发送路由更新包
     */
    private void periodicUpdate() {
        // 仅在引擎正在运行时执行周期性更新
        if (!running) return;
        for (Router r : routers.values()) {
            // 创建不应用水平分割的更新包（在发送时处理水平分割）
            RIPPacket packet = r.createUpdatePacket(false, null);
            // 遍历该路由器的所有邻居
            for (Map.Entry<String,Integer> nei : r.getNeighbors().entrySet()) {
                String neighborId = nei.getKey();
                Router neighbor = routers.get(neighborId);
                if (neighbor == null) continue;
                // 应用水平分割策略
                RIPPacket toSend = applySplitHorizon(packet, r.getId(), neighborId, r);
                // 将更新包传递给邻居路由器
                deliverPacket(neighbor, toSend, r.getId(), r.getNeighbors().get(neighborId));
            }
        }
    }

    /**
     * 应用水平分割策略到路由更新包
     * 根据配置决定使用普通水平分割还是毒性逆转
     * @param packet 原始路由更新包
     * @param fromId 发送路由器ID
     * @param toNeighborId 目标邻居路由器ID
     * @param fromRouter 发送路由器实例
     * @return 应用了水平分割策略的新路由更新包
     */
    private RIPPacket applySplitHorizon(RIPPacket packet, String fromId, String toNeighborId, Router fromRouter) {
        RIPPacket p = new RIPPacket();
        p.setFromRouterId(fromId);
        List<RouteEntry> entries = new ArrayList<>();
        for (RouteEntry e : packet.getEntries()) {
            RouteEntry copy = new RouteEntry();
            copy.setDestination(e.getDestination());
            copy.setNextHop(e.getNextHop());
            copy.setLastUpdated(e.getLastUpdated());
            int metric = e.getMetric();
            
            // 根据配置应用不同的水平分割行为
            if (config.getSplitHorizonMode() == SimulationConfig.SplitHorizonMode.SPLIT_HORIZON) {
                // 普通水平分割：不将从某个邻居学到的路由再通告给该邻居
                if (e.getNextHop() != null && e.getNextHop().equals(toNeighborId)) {
                    continue; // 省略该条目
                }
            } else if (config.getSplitHorizonMode() == SimulationConfig.SplitHorizonMode.POISON_REVERSE) {
                // 毒性逆转：将从某个邻居学到的路由以无穷大度量值通告给该邻居
                if (e.getNextHop() != null && e.getNextHop().equals(toNeighborId)) {
                    metric = config.getMaxMetric(); // 设置为最大度量值（表示不可达）
                }
            }
            copy.setMetric(metric);
            entries.add(copy);
        }
        p.setEntries(entries);
        p.setTriggered(packet.isTriggered());
        return p;
    }

    /**
     * 处理接收到的路由更新包
     * 更新目标路由器的路由表并根据需要触发相关操作
     * @param dst 目标路由器
     * @param packet 接收到的路由更新包
     * @param fromId 发送方路由器ID
     * @param linkCost 到发送方的链路成本
     */
    private void deliverPacket(Router dst, RIPPacket packet, String fromId, int linkCost) {
        boolean changed = false;
        // 遍历更新包中的所有路由条目
        for (RouteEntry rec : packet.getEntries()) {
            String dest = rec.getDestination();
            int recvMetric = rec.getMetric();
            // 计算新的度量值（考虑链路成本）
            int newMetric;
            if (recvMetric >= config.getMaxMetric()) newMetric = config.getMaxMetric();
            else {
                newMetric = recvMetric + linkCost;
                if (newMetric > config.getMaxMetric()) newMetric = config.getMaxMetric();
            }

            // 获取目标路由器当前的路由条目
            RouteEntry current = dst.getRoutingTable().get(dest);

            // 检查路由抑制：如果当前处于抑制状态且新路由来自不同下一跳且度量值更小，则忽略
            Map<String, Instant> holdMap = holdDownExpiry.getOrDefault(dst.getId(), Collections.emptyMap());
            Instant holdExpire = holdMap.get(dest);
            boolean inHoldDown = holdExpire != null && Instant.now().isBefore(holdExpire);

            if (current == null) {
                // 如果当前没有该目的地的路由且新度量值有效，则添加新路由
                if (newMetric < config.getMaxMetric()) {
                    RouteEntry ne = new RouteEntry();
                    ne.setDestination(dest);
                    ne.setMetric(newMetric);
                    ne.setNextHop(fromId);
                    ne.setLastUpdated(Instant.now());
                    ne.setChanged(true);
                    dst.getRoutingTable().put(dest, ne);
                    changed = true;
                }
            } else {
                // 如果路由来自相同的邻居，则接受更新（无论度量值增加还是减少）
                if (Objects.equals(current.getNextHop(), fromId)) {
                    // 接受度量值变化
                    if (current.getMetric() != newMetric) {
                        // 如果度量值增加（变差）并达到最大值，则启动路由抑制
                        if (newMetric >= config.getMaxMetric()) {
                            startHoldDown(dst.getId(), dest);
                        }
                        current.setMetric(newMetric);
                        current.setLastUpdated(Instant.now());
                        current.setChanged(true);
                        dst.getRoutingTable().put(dest, current);
                        changed = true;
                        // 如果启用了触发更新，则立即发送
                        if (config.isEnableTriggeredUpdates()) sendTriggeredUpdateFrom(dst.getId());
                    }
                } else {
                    // 路由来自不同的邻居
                    // 如果处于路由抑制状态，忽略来自其他邻居的改进（基本的抑制行为）
                    if (inHoldDown && newMetric < current.getMetric()) {
                        // 忽略更新
                    } else {
                        // 如果新度量值更好或者当前路由不可达，则接受更新
                        if (newMetric < current.getMetric() || current.getMetric() >= config.getMaxMetric()) {
                            current.setMetric(newMetric);
                            current.setNextHop(fromId);
                            current.setLastUpdated(Instant.now());
                            current.setChanged(true);
                            dst.getRoutingTable().put(dest, current);
                            changed = true;
                            if (config.isEnableTriggeredUpdates()) sendTriggeredUpdateFrom(dst.getId());
                        } else if (newMetric > current.getMetric()) {
                            // 如果度量值变差并变得不可达，则启动路由抑制并更新
                            if (newMetric >= config.getMaxMetric()) {
                                current.setMetric(newMetric);
                                current.setLastUpdated(Instant.now());
                                current.setChanged(true);
                                dst.getRoutingTable().put(dest, current);
                                startHoldDown(dst.getId(), dest);
                                changed = true;
                                if (config.isEnableTriggeredUpdates()) sendTriggeredUpdateFrom(dst.getId());
                            }
                        }
                    }
                }
            }
        }
        // 如果路由表发生变化且存在监听器，则通知监听器
        if (changed && listener != null) {
            listener.onRoutingTableChanged(simulationId, dst.getId(), dst.getRoutingTable());
        }
    }

    /**
     * 启动路由抑制计时器
     * 当路由变为不可达时调用，防止路由抖动
     * @param routerId 路由器ID
     * @param dest 目标网络
     */
    private void startHoldDown(String routerId, String dest) {
        Instant expire = Instant.now().plusSeconds(config.getHoldDownSeconds());
        holdDownExpiry.computeIfAbsent(routerId, k -> new ConcurrentHashMap<>()).put(dest, expire);
    }

    /**
     * 检查路由超时和清理过期的抑制计时器
     * 定期执行以维护路由表的正确性和清理资源
     */
    private void checkTimeoutsAndHolds() {
        Instant now = Instant.now();
        for (Router r : routers.values()) {
            boolean changed = false;
            // 检查路由表中的每个条目是否超时
            // 先拷贝一份条目快照以避免在迭代时对底层集合的并发修改
            for (RouteEntry e : new ArrayList<>(r.getRoutingTable().entries())) {
                // 跳过本地路由
                if (e.getNextHop() != null && e.getNextHop().equals(r.getId())) continue;
                if (e.getLastUpdated() == null) continue;
                
                // 计算路由条目的存活时间
                long ageSeconds = java.time.Duration.between(e.getLastUpdated(), now).getSeconds();
                // 如果超过配置的超时时间且当前不是不可达状态，则标记为不可达并启动抑制
                if (ageSeconds > config.getRouteTimeoutSeconds() && e.getMetric() < config.getMaxMetric()) {
                    e.setMetric(config.getMaxMetric());
                    e.setLastUpdated(Instant.now());
                    e.setChanged(true);
                    r.getRoutingTable().put(e.getDestination(), e);
                    startHoldDown(r.getId(), e.getDestination());
                    changed = true;
                }
            }
            
            // 清理该路由器过期的路由抑制计时器
            Map<String, Instant> hmap = holdDownExpiry.get(r.getId());
            if (hmap != null) {
                List<String> toRemove = new ArrayList<>();
                for (var kv : hmap.entrySet()) {
                    // 收集已过期的抑制条目
                    if (Instant.now().isAfter(kv.getValue())) toRemove.add(kv.getKey());
                }
                // 移除过期的抑制条目
                for (String k : toRemove) hmap.remove(k);
            }
            
            // 如果路由表发生变化且存在监听器，则通知监听器
            if (changed && listener != null) listener.onRoutingTableChanged(simulationId, r.getId(), r.getRoutingTable());
        }
    }

    /**
     * 立即向邻居发送触发更新
     * 当路由发生变化时调用，以快速传播路由信息
     * @param routerId 发送触发更新的路由器ID
     */
    private void sendTriggeredUpdateFrom(String routerId) {
        Router r = routers.get(routerId);
        if (r == null) return;
        // 创建不应用水平分割的更新包（在发送时处理水平分割）
        RIPPacket packet = r.createUpdatePacket(false, null);
        packet.setTriggered(true); // 标记为触发更新
        // 向所有邻居发送更新包
        for (Map.Entry<String,Integer> nei : r.getNeighbors().entrySet()) {
            String neighborId = nei.getKey();
            Router neighbor = routers.get(neighborId);
            if (neighbor == null) continue;
            // 应用水平分割策略
            RIPPacket toSend = applySplitHorizon(packet, r.getId(), neighborId, r);
            // 将更新包传递给邻居路由器
            deliverPacket(neighbor, toSend, r.getId(), r.getNeighbors().get(neighborId));
        }
    }
}